#!/usr/bin/python3
# -*- coding:UTF-8 -*-

import os
import traceback
import time
import json
import argparse
import re
import pywbem


import AutoExecUtils


class HP3parCollector:
    def __init__(self, host, userName, password, timeout, inspect):
        self.data = {}
        self.host = host
        if timeout is None:
            self.timeout = 30
        else:
            self.timeout = timeout
        self.inspect = inspect

        self.iGroupMap = None
        self.dev2GroupMap = None
        self.devId2NodeMap = None
        self.devName2NodeMap = None
        self.poolMapByInsId = None

        try:
            conn = pywbem.WBEMConnection('https://' + host, (userName, password), default_namespace='root/tpd', no_verification=True)
            self.conn = conn
        except:
            conn = None
            print("ERROR: Could not create smi-s connection to {} with user {}".format(host, userName))
            exit(-1)

    def wwnAddColon(self, wwn):
        pattern = re.compile('.{2}')
        return ':'.join(pattern.findall(wwn))

    def getCIMClassInstances(self, className):
        conn = self.conn
        # 返回所有的 pywbem.CIMInstance
        # 所有属性都在CIMInstance的properties dict集合元素是 pywbem.CIMProperty
        # 如果查询的是关联对象的CIMInstance，则里面的Property会表示两个对象的关联，关联使用的对象是
        # CIMInstanceName来进行关联，CIMInstanceName表示CIMInsntace的唯一路径
        instances = []
        try:
            instances = conn.EnumerateInstances(className)
        except ex as Exception:
            print('WARN: Enumerate instance for class ' + className + ' Failed, ' + str(ex))

        return instances

    def getHealthStateDesc(self, healthCode):
        code2Txt = {
            30: "Non-recoverable Error",
            25: "Critical Failure",
            20: "Major Failure",
            15: "Minor Failure",
            10: "Degraded/Warning",
            5: "OK",
            0: "Unknown"
        }
        return code2Txt.get(healthCode)

    def getDev2GroupMap(self):
        if self.dev2GroupMap is not None:
            return self.dev2GroupMap

        dev2GroupMap = {}
        instances = self.getCIMClassInstances('CIM_SystemDevice')
        for ins in instances:
            groupComponent = ins.get('GroupComponent').get('Name')
            partComponent = ins.get('PartComponent').get('DeviceID')
            dev2GroupMap[partComponent] = groupComponent

        self.dev2GroupMap = dev2GroupMap
        return dev2GroupMap

    def getDeviceInfo(self):
        instances = self.getCIMClassInstances('TPD_StorageSystem')
        sysIns = instances[0]
        self.data['DEV_NAME'] = sysIns.get('ElementName')
        self.data['MACHINE_ID'] = sysIns.get('Name')
        descs = sysIns.get('Description').split(',')
        self.data['MODEL'] = descs[0]
        for desc in descs:
            matchObj = re.match(r'Serial\s+number:\s*(\S*?)$', desc, re.IGNORECASE)
            if matchObj:
                self.data['SN'] = matchObj.group(1)
                continue
            matchObj = re.match(r'OS\s+version:\s*(\S*?)$', desc, re.IGNORECASE)
            if matchObj:
                self.data['IOS_VERSION'] = matchObj.group(1)
                continue

    def getControllerNameMap(self):
        self.getControllersMap()
        return self.devName2NodeMap

    def getControllersMap(self):
        if self.devName2NodeMap is not None:
            return self.devName2NodeMap

        devName2NodeMap = {}
        nodes = []
        instances = self.getCIMClassInstances('TPD_NodeSystem')
        for ins in instances:
            name = ins.get('Name')
            eleName = ins.get('ElementName')
            nodeInfo = {
                'NAME': eleName,
                'DESC': ins.get('Description'),
                'SN': ins.get('Name'),
                'IOS_VERSION': ins.get('KernelVersion'),
                'STATUS': self.getHealthStateDesc(ins.get('HealthState'))
            }
            nodes.append(nodeInfo)
            devName2NodeMap[name] = nodeInfo
        self.data['CONTROLLERS'] = nodes
        self.devName2NodeMap = devName2NodeMap

        return devName2NodeMap

    def getControlerFCPortMap(self):
        controllerFcPortMap = {}
        # CIM类CIM_SystemFCPort是Controller节点和FC口的关系类，关系类通过value中的keybindings属性来关联
        instances = self.getCIMClassInstances('TPD_SystemFCPort')
        for ins in instances:
            controllerName = ins.get('GroupComponent').get('Name')
            fcDevId = ins.get('PartComponent').get('DeviceID')
            controllerFcPortMap[fcDevId] = controllerName
        return controllerFcPortMap

    def getInitiators(self):
        if self.iGroupMap is not None:
            return self.iGroupMap

        iGroupMap = {}
        initiatorsMap = {}
        instances = self.getCIMClassInstances('CIM_SCSIProtocolController')
        for ins in instances:
            iGroupName = ins.get('ElementName')
            devId = ins.get('DeviceID')
            iGroup = {
                'NAME': iGroupName,
                'DEVICE_ID': devId,
                'MEMBERS': []
            }
            initiatorsMap[iGroupName] = iGroup
            iGroupMap[devId] = iGroup

        instances = self.getCIMClassInstances('TPD_MemberOfStorageHardwareIDCollection')
        for ins in instances:
            iGroupName = ins.get('Collection').get('InstanceID').split(':')[1]
            member = ins.get('Member').get('InstanceID').split(':')[1]
            (memType, memAddr) = member.split('-')

            iGroup = initiatorsMap.get(iGroupName)
            if iGroup is not None:
                memberInfo = {
                    'TYPE': memType,
                    'WWN': self.wwnAddColon(memAddr)
                }
                iGroup['MEMBERS'].append(memberInfo)

        return iGroupMap

    def getAllocatedMap(self):
        allocatedMap = {}
        for mapClass in ['CIM_AllocatedFromStoragePool']:
            instances = self.getCIMClassInstances(mapClass)
            for ins in instances:
                poolInstanceId = ins.get('Antecedent').get('InstanceID')
                depInsName = ins.get('Dependent')
                uuid = depInsName.get('DeviceID')
                if not uuid:
                    uuid = depInsName.get('InstanceId')
                allocatedMap[uuid] = poolInstanceId
        return allocatedMap

    def getPoolInfo(self):
        if self.poolMapByInsId is not None:
            return self.poolMapByInsId

        poolMapByInsId = {}
        pools = []
        for poolClass in ['CIM_StoragePool']:
            instances = self.getCIMClassInstances(poolClass)
            for ins in instances:
                name = ins.get('ElementName')
                instanceId = ins.get('InstanceID')
                status = self.getHealthStateDesc(ins.get('HealthState'))

                poolType = 'Concrete'
                thinProvision = ins.get('ThinProvisionMetaDataSpace')
                totalManagedSize = ins.get('TotalManagedSpace')
                spaceLimitDetermination = ins.get('SpaceLimitDetermination')

                if thinProvision is None:
                    if totalManagedSize == 0:
                        poolType = 'Empty'
                    else:
                        poolType = 'Dynamic'

                capacity = round(totalManagedSize / 1000 / 1000 / 1000, 2)
                spaceLimit = round(ins.get('SpaceLimit') / 1000 / 1000 / 1000, 2)
                if capacity == 0:
                    capacity = spaceLimit

                available = round(ins.get('RemainingManagedSpace')/1000/1000/1000, 2)
                used = capacity - available
                if used < 0:
                    used = capacity

                poolInfo = {
                    'NAME': name,
                    'TYPE': poolType,
                    'CAPACITY': capacity,
                    'AVAILABLE': available,
                    'USED': used,
                    'STATUS': status
                }
                if spaceLimitDetermination == 2:
                    poolInfo['USED_PCT'] = round(used * 100/capacity, 2)

                pools.append(poolInfo)
                poolMapByInsId[instanceId] = poolInfo
        self.data['POOLS'] = pools
        self.poolMapByInsId = poolMapByInsId

        return poolMapByInsId

    def getLunInfo(self):
        poolMapByInsId = self.getPoolInfo()
        lunInPoolMap = self.getAllocatedMap()
        iGroupMap = self.getInitiators()
        #print(json.dumps(iGroupMap, indent=4, ensure_ascii=True))
        lunsMap = {}
        instances = self.getCIMClassInstances('CIM_StorageVolume')
        for ins in instances:
            wwn = ins.get('DeviceID')

            poolInsId = lunInPoolMap[wwn]
            poolInfo = poolMapByInsId[poolInsId]

            name = ins.get('ElementName')
            status = self.getHealthStateDesc(ins.get('HealthState'))

            blockSize = ins.get('BlockSize')
            numberOfBlocks = ins.get('NumberOfBlocks')
            consumableBlocks = ins.get('ConsumableBlocks')
            capacity = round(numberOfBlocks * blockSize / 1000 / 1000 / 1000, 2)
            used = round(consumableBlocks * blockSize / 1000 / 1000 / 1000, 2)
            lunInfo = {
                'NAME': name,
                'WWN': wwn,
                'CAPACITY': capacity,
                'USED': used,
                'USED_PCT': round(used * 100 / capacity, 2),
                'POOL_NAME': poolInfo['NAME'],
                'STATUS': status,
                'VISABLE_GROUPS': [],
                'VISABLE_INITIATORS': []
            }
            lunsMap[wwn] = lunInfo
            # poolInfo['LUNS'].append(lunInfo)

        instances = self.getCIMClassInstances('CIM_ProtocolControllerForUnit')
        for ins in instances:
            lunWWN = ins.get('Dependent').get('DeviceID')
            iGroupId = ins.get('Antecedent').get('DeviceID')
            iGroup = iGroupMap[iGroupId]
            lunInfo = lunsMap[lunWWN]
            lunInfo['VISABLE_GROUPS'].append(iGroup['NAME'])
            members = {}
            for wwnInfo in iGroup['MEMBERS']:
                members[wwnInfo['WWN']] = 1
            for wwn in members.keys():
                lunInfo['VISABLE_INITIATORS'].append(wwn)

        self.data['LUNS'] = list(lunsMap.values())

    def getHealthInfo(self):
        servities = ['NULL', 'DEBUG', 'INFORMATIONAL', 'DEGRADED', 'MINOR', 'MAJOR', 'CRITICAL', 'FATAL']

        content = ''
        instances = self.getCIMClassInstances('CIM_AlertIndication')
        for ins in instances:
            if 'IndicationTime' in ins.properties:
                indicationTime = str(ins.get('IndicationTime'))
                content = content + indicationTime + "\t"
            if 'PerceivedSeverity' in ins.properties:
                severity = servities[ins.get('PerceivedSeverity')]
                content = content + severity + "\t"
            if 'IndicationIdentifier' in ins.properties:
                indentifier = str(ins.get('IndicationIdentifier'))
                content = content + indentifier + "\t"
            if 'Message' in ins.properties:
                message = str(ins.get('Message'))
                content = content + message + "\t"
            content = content + "\n"
        self.data['HEALTH_CHECK'] = content

    def getEthInfo(self):
        pass

    def getFcInfo(self):
        controllersMap = self.getControllerNameMap()
        fcControllerMap = self.getDev2GroupMap()

        hbas = []
        instances = self.getCIMClassInstances('CIM_FCPort')
        for ins in instances:
            connectTo = []
            connectToStr = ins.get('ConnectedTo')
            if connectToStr is not None:
                connectTo = connectToStr.split(';')

            deviceId = ins.get('DeviceID')
            controllerName = fcControllerMap[deviceId]

            hbaInfo = {
                'NAME': ins.get('ElementName'),
                'CONTROLLER_NAME': controllersMap.get(controllerName).get('DESC'),
                'WWNN': self.wwnAddColon(hex(ins.get('NodeWWN'))[2:]),
                'WWPN': self.wwnAddColon(ins.get('PermanentAddress')),
                'SPEED': int(ins.get('MaxSpeed')/1000/1000/1000),
                'STATUS': self.getHealthStateDesc(ins.get('HealthState')),
                'CONNECT_TO': connectTo
            }
            hbas.append(hbaInfo)

        self.data['HBA_INTERFACES'] = hbas

    def getIPAddrs(self):
        pass

    def getFanPowSupplyMap(self):
        fanPowSupplyMap = {}
        instances = self.getCIMClassInstances('CIM_AssociatedCooling')
        for ins in instances:
            fanDevId = ins.get('Antecedent').get('DeviceID')
            powSupplyDevId = ins.get('Dependent').get('DeviceID')
            fanPowSupplyMap[fanDevId] = powSupplyDevId
        return fanPowSupplyMap

    def getFanInfo(self):
        fans = []
        fanPowSupplyMap = self.getFanPowSupplyMap()
        instances = self.getCIMClassInstances('CIM_Fan')
        for ins in instances:
            name = ins.get('ElementName')
            devId = ins.get('DeviceID')
            status = self.getHealthStateDesc(ins.get('HealthState'))
            speed = ins.get('VariableSpeed')
            powSupplyDevId = fanPowSupplyMap.get(devId)
            fanInfo = {
                'NAME': name,
                'DEVICE_ID': devId,
                'SPEED': speed,
                'POWERSUPPLY_NAME': powSupplyDevId,
                'STATUS': status
            }
            fans.append(fanInfo)
        self.data['FANS'] = fans

    def getPowerInfo(self):
        controllersMap = self.getControllersMap()
        powSupplies = []
        instances = self.getCIMClassInstances('CIM_PowerSupply')
        for ins in instances:
            devId = ins.get('DeviceID')
            name = ins.get('ElementName')
            status = self.getHealthStateDesc(ins.get('HealthState'))
            sysNodeDevId = self.getDev2GroupMap().get(devId)
            powSupplyInfo = {
                'NAME': name,
                'DEVICE_ID': devId,
                'STATUS': status
            }
            sysNodeDevInfo = controllersMap.get(sysNodeDevId)
            if sysNodeDevInfo is not None:
                powSupplyInfo['CONTROLLER_NAME'] = sysNodeDevInfo.get('DESC')
            else:
                powSupplyInfo['CONTROLLER_NAME'] = None

            powSupplies.append(powSupplyInfo)
        self.data['POWER_SUPPLIES'] = powSupplies

    def getBatteryInfo(self):
        controllersMap = self.getControllersMap()
        batteries = []
        instances = self.getCIMClassInstances('CIM_Battery')
        for ins in instances:
            devId = ins.get('DeviceID')
            name = ins.get('ElementName')
            status = self.getHealthStateDesc(ins.get('HealthState'))
            sysNodeDevId = self.getDev2GroupMap().get(devId)
            batteryInfo = {
                'NAME': name,
                'DEVICE_ID': devId,
                'STATUS': status
            }
            sysNodeDevInfo = controllersMap.get(sysNodeDevId)
            if sysNodeDevInfo is not None:
                batteryInfo['CONTROLLER_NAME'] = sysNodeDevInfo.get('DESC')
            else:
                batteryInfo['CONTROLLER_NAME'] = None

            batteries.append(batteryInfo)
        self.data['BATTERIES'] = batteries

    def healthCheck(self):
        pass

    def collect(self):
        print("INFO: Try to colelct device information.")
        self.getDeviceInfo()
        print("INFO: Try to colelct fc information.")
        self.getFcInfo()
        print("INFO: Try to colelct ethernet information.")
        self.getEthInfo()
        print("INFO: Try to colelct power information.")
        self.getPowerInfo()
        print("INFO: Try to colelct battery information.")
        self.getBatteryInfo()
        print("INFO: Try to colelct fan information.")
        self.getFanInfo()
        print("INFO: Try to colelct pool information.")
        self.getPoolInfo()
        print("INFO: Try to colelct lun information.")
        self.getLunInfo()
        print("INFO: Try to colelct ip address information.")
        self.getIPAddrs()

        if self.inspect == 1:
            print("INFO: Try to do health check.")
            self.healthCheck()

        self.data['_OBJ_CATEGORY'] = 'STORAGE'
        self.data['_OBJ_TYPE'] = 'Storage'
        self.data['BRAND'] = 'HP-3Par'
        self.data['VENDOR'] = 'HP'
        self.data['MODEL'] = 'HP-3Par'
        self.data['APP_TYPE'] = 'HP-3Par'
        self.data['PK'] = ['MGMT_IP']
        print("INFO: Information collected.")
        return self.data


def usage():
    pass


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--node', default='', help='Execution node json')
    parser.add_argument('--verbose', default=0, help='Verbose')
    parser.add_argument('--timeout', default=10, help='Timeout seconds')
    parser.add_argument('--inspect', default=0, help='Health check')
    parser.add_argument('otherthings', nargs=argparse.REMAINDER)

    args = parser.parse_args()

    inspect = int(args.inspect)
    timeOut = int(args.timeout)
    verbose = int(args.verbose)

    if timeOut == 0:
        timeOut = 5

    node = args.node

    try:
        nodeInfo = {}
        hasOptError = False
        if node is None or node == '':
            node = os.getenv('AUTOEXEC_NODE')
        if node is None or node == '':
            print("ERROR: Can not find node definition.")
            hasOptError = True
        else:
            nodeInfo = json.loads(node)

        if hasOptError:
            usage()

        hasError = False

        ip = nodeInfo['host']
        # port = nodeInfo['protocolPort']
        username = nodeInfo['username']
        password = nodeInfo['password']

        hp3parCollector = HP3parCollector(ip, username, password, timeOut, inspect)
        data = hp3parCollector.collect()
        data['RESOURCE_ID'] = nodeInfo.get('resourceId')
        data['MGMT_IP'] = nodeInfo.get('host')
        out = {'DATA': [data]}
        AutoExecUtils.saveOutput(out)
        if verbose == 1:
            print(json.dumps(data, ensure_ascii=True, indent=4))
    except Exception as ex:
        errorMsg = str(ex)
        print("ERROR: ", errorMsg)
        traceback.print_exc()
